﻿using System;
using System.Collections.Generic;
using System.IO;
using Fungus;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using BepInEx.Logging;
using CheatMenu.Managers;

namespace CheatMenu.Utils
{
    public static class SceneUtil
    {
        public static string[] GetSceneNames()
        {
            int sceneCount = SceneManager.sceneCountInBuildSettings;
            string[] scenes = new string[sceneCount];

            for (int i = 0; i < sceneCount; i++)
            {
                scenes[i] = Path.GetFileNameWithoutExtension(SceneUtility.GetScenePathByBuildIndex(i));
            }

            return scenes;
        }

        public static void DumpScene()
        {
            Scene activeScene = SceneManager.GetActiveScene();
            GameObject[] rootGameObjects = activeScene.GetRootGameObjects();
            using (FileStream fileStream = File.Create(Path.Combine(Application.dataPath, activeScene.name + ".dmp")))
            {
                using (StreamWriter streamWriter = new StreamWriter(fileStream))
                {
                    try
                    {
                        foreach (GameObject gameObject in rootGameObjects)
                        {
                            streamWriter.WriteLine("{0}", gameObject.name);
                            Dictionary<Block, List<Fungus.Command>> dictionary = new Dictionary<Block, List<Fungus.Command>>();
                            foreach (Block key in gameObject.GetComponents<Block>())
                            {
                                dictionary.Add(key, new List<Fungus.Command>());
                            }
                            foreach (Fungus.Command command in gameObject.GetComponents<Fungus.Command>())
                            {
                                dictionary[command.ParentBlock].Add(command);
                            }
                            foreach (KeyValuePair<Block, List<Fungus.Command>> keyValuePair in dictionary)
                            {
                                keyValuePair.Value.Sort((Fungus.Command s1, Fungus.Command s2) => s1.CommandIndex.CompareTo(s2.CommandIndex));
                                streamWriter.WriteLine("\t(Block){0}", keyValuePair.Key.BlockName);
                                foreach (Fungus.Command command2 in keyValuePair.Value)
                                {
                                    Type type = command2.GetType();
                                    string text = FormatIndent(2, command2);
                                    if (type == typeof(SaveVariable))
                                    {
                                        SaveVariable saveVariable = (SaveVariable)command2;
                                        text += string.Format("(SaveVariable) {0}", ReflectionUtil.GetKeyFromSaveVariable(saveVariable));
                                    }
                                    else if (type == typeof(LoadVariable))
                                    {
                                        LoadVariable loadVariable = (LoadVariable)command2;
                                        text += string.Format("(LoadVariable) {0}", ReflectionUtil.GetKeyFromLoadVariable(loadVariable));
                                    }
                                    else if (type == typeof(Call))
                                    {
                                        Call call = (Call)command2;
                                        Block targetBlock = ReflectionUtil.GetTargetBlockFromCall(call);
                                        string targetBlockName = targetBlock?.BlockName ?? "null";
                                        Flowchart targetFlowchart = ReflectionUtil.GetTargetFlowchartFromCall(call);
                                        string targetFlowchartName = targetFlowchart?.name ?? "null";
                                        text += string.Format("(Call) {0} - {1}", targetBlockName, targetFlowchartName);
                                    }
                                    else if (type == typeof(SetVariable))
                                    {
                                        text += string.Format("(SetVariable) {0}", FormatSetVariable((SetVariable)command2));
                                    }
                                    else if (type == typeof(Say))
                                    {
                                        Say say = (Say)command2;
                                        Character character = ReflectionUtil.GetCharacterFromSay(say);
                                        string storyText = ReflectionUtil.GetStoryTextFromSay(say);
                                        text += string.Format("(Say) {0} - {1}", (character != null) ? character.NameText : "null", (storyText != null) ? storyText.Replace("\r", "").Replace("\n", "") : "null");
                                    }
                                    else if (type == typeof(LoadScene))
                                    {
                                        LoadScene loadScene = (LoadScene)command2;
                                        StringData sceneName = ReflectionUtil.GetSceneNameFromLoadScene(loadScene);
                                        text += string.Format("(LoadScene) {0}", sceneName.Value);
                                    }
                                    else if (type == typeof(If))
                                    {
                                        If condition = (If)command2;
                                        text += string.Format("(If) {0}", FormatVariable(condition));
                                    }
                                    else if (type == typeof(ElseIf))
                                    {
                                        text += string.Format("(ElseIf) {0}", FormatVariable((ElseIf)command2));
                                    }
                                    else if (type == typeof(Menu))
                                    {
                                        Menu menu = (Menu)command2;
                                        string menuText = ReflectionUtil.GetTextFromMenu(menu);
                                        Block targetBlock = ReflectionUtil.GetTargetBlockFromMenu(menu);
                                        string targetBlockName = targetBlock?.BlockName ?? "null";
                                        text += string.Format("(Menu) {0}", string.Format("{0} - {1}", menuText, targetBlockName));
                                    }
                                    else if (type == typeof(SetActive))
                                    {
                                        SetActive setActive = (SetActive)command2;
                                        GameObjectData targetGameObject = ReflectionUtil.GetTargetGameObjectFromSetActive(setActive);
                                        GameObject targetGameObjectValue = targetGameObject.Value;
                                        string gameObjectName = targetGameObjectValue?.name ?? "null";
                                        text += string.Format("(SetActive) {0} {1}", gameObjectName, ReflectionUtil.GetActiveStateFromSetActive(setActive).Value);
                                    }
                                    else if (type == typeof(SetText))
                                    {
                                        SetText setText = (SetText)command2;
                                        StringData setTextTextData = ReflectionUtil.GetTextFromSetText(setText);
                                        string setTextText = "null";
                                        if (setTextTextData.Value != null)
                                            setTextText = setTextTextData.Value.Replace("\r", "").Replace("\n", "");
                                        text += string.Format("(SetText) {0}", setTextText);
                                    }
                                    else if (type == typeof(RandomInteger))
                                    {
                                        RandomInteger ranInteger = (RandomInteger)command2;
                                        IntegerVariable ranIntegerVariable = ReflectionUtil.GetVariableFromRandomInteger(ranInteger);
                                        string ranIntegerVariableKey = ranIntegerVariable?.Key ?? "null";
                                        text += string.Format("(RandomInteger) Min:{0} Max:{1} - {2}", ReflectionUtil.GetMinValueFromRandomInteger(ranInteger).Value, ReflectionUtil.GetMaxValueFromRandomInteger(ranInteger).Value, ranIntegerVariableKey);
                                    }
                                    else if (type == typeof(InvokeEvent))
                                    {
                                        InvokeEvent invokeEvent = (InvokeEvent)command2;
                                        UnityEventBase unityEvent = null;
                                        InvokeType invokeType = ReflectionUtil.GetInvokeTypeFromInvokeEvent(invokeEvent);
                                        switch (invokeType)
                                        {
                                            case InvokeType.Static:
                                                unityEvent = ReflectionUtil.GetStaticEventFromInvokeEvent(invokeEvent);
                                                break;
                                            case InvokeType.DynamicBoolean:
                                                unityEvent = ReflectionUtil.GetBooleanEventFromInvokeEvent(invokeEvent);
                                                break;
                                            case InvokeType.DynamicInteger:
                                                unityEvent = ReflectionUtil.GetIntegerEventFromInvokeEvent(invokeEvent);
                                                break;
                                            case InvokeType.DynamicFloat:
                                                unityEvent = ReflectionUtil.GetFloatEventFromInvokeEvent(invokeEvent);
                                                break;
                                            case InvokeType.DynamicString:
                                                unityEvent = ReflectionUtil.GetStringEventFromInvokeEvent(invokeEvent);
                                                break;
                                        }
                                        int eventCount = unityEvent.GetPersistentEventCount();
                                        string methodNames = eventCount == 0 ? "null" : "";
                                        for (int i = 0; i < eventCount; i++)
                                        {
                                            methodNames += string.Format(" {0}", unityEvent.GetPersistentMethodName(i));
                                        }
                                        text += string.Format("(InvokeEvent) Type: {0} - {1}", invokeType, methodNames);
                                    }
                                    else if (type == typeof(Else) || type == typeof(End) || type == typeof(FadeScreen) || type == typeof(Wait) || type == typeof(SetMenuDialog))
                                    {
                                        text += string.Format("({0})", type.Name);
                                    }
                                    else
                                    {
                                        text += string.Format("({0}) - {1}", type.Name, command2.ToString().Replace("(" + type.FullName + ")", ""));
                                    }
                                    streamWriter.WriteLine(text);
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        string failedDump = string.Format("ERROR; REPORT THIS, Exception: {0}", ex);
                        LogManager.LogError($"DumpScene Exception: " + ex);
                        streamWriter.WriteLine(failedDump);
                    }
                }
            }
        }

        private static string FormatIndent(int baseCount, Fungus.Command command)
        {
            int num = command.IndentLevel + baseCount;
            string text = "";
            for (int i = 0; i < num; i++)
            {
                text += "\t";
            }
            return text;
        }

        private static string FormatVariable(VariableCondition condition)
        {
            Variable variable = ReflectionUtil.GetVariableFromVariableCondition(condition);
            string result = "-variable null-";
            if (variable != null)
            {
                Type type = variable.GetType();
                if (type == typeof(IntegerVariable))
                {
                    result = string.Format("(int){0} {1} {2}", variable.Key, FormatCompareOperator(condition), ReflectionUtil.GetIntegerDataFromVariableCondition(condition).Value);
                }
                else if (type == typeof(BooleanVariable))
                {
                    result = string.Format("(bool){0} {1} {2}", variable.Key, FormatCompareOperator(condition), ReflectionUtil.GetBooleanDataFromVariableCondition(condition).Value);
                }
                else if (type == typeof(StringVariable))
                {
                    result = string.Format("(string){0} {1} {2}", variable.Key, FormatCompareOperator(condition), ReflectionUtil.GetStringDataFromVariableCondition(condition).Value);
                }
                else if (type == typeof(FloatVariable))
                {
                    result = string.Format("(float){0} {1} {2}", variable.Key, FormatCompareOperator(condition), ReflectionUtil.GetFloatDataFromVariableCondition(condition).Value);
                }
                else
                {
                    result = string.Format("{0} {1} Unknown", variable.Key, FormatCompareOperator(condition));
                }
            }
            return result;
        }

        private static string FormatSetVariable(SetVariable setVariable)
        {
            Variable variable = ReflectionUtil.GetVariableFromSetVariable(setVariable);
            string result = "null";
            if (variable != null)
            {
                Type type = variable.GetType();
                if (type == typeof(IntegerVariable))
                {
                    result = string.Format("(int){0} {1} {2}", variable.Key, FormatSetOperator(setVariable), ReflectionUtil.GetIntegerDataFromSetVariable(setVariable).Value);
                }
                else if (type == typeof(BooleanVariable))
                {
                    result = string.Format("(bool){0} {1} {2}", variable.Key, FormatSetOperator(setVariable), ReflectionUtil.GetBooleanDataFromSetVariable(setVariable).Value);
                }
                else if (type == typeof(StringVariable))
                {
                    result = string.Format("(string){0} {1} {2}", variable.Key, FormatSetOperator(setVariable), ReflectionUtil.GetStringDataFromSetVariable(setVariable).Value);
                }
                else if (type == typeof(FloatVariable))
                {
                    result = string.Format("(float){0} {1} {2}", variable.Key, FormatSetOperator(setVariable), ReflectionUtil.GetFloatDataFromSetVariable(setVariable).Value);
                }
                else
                {
                    result = string.Format("(Unknown){0} {1}", variable.Key, setVariable);
                }
            }
            return result;
        }

        private static string FormatSetOperator(SetVariable setVariable)
        {
            string result;
            SetOperator setOperator = ReflectionUtil.GetSetOperatorFromSetVariable(setVariable);
            switch (setOperator)
            {
                case SetOperator.Assign:
                    result = "=";
                    break;
                case SetOperator.Negate:
                    result = "=!";
                    break;
                case SetOperator.Add:
                    result = "+";
                    break;
                case SetOperator.Subtract:
                    result = "-";
                    break;
                case SetOperator.Multiply:
                    result = "*";
                    break;
                case SetOperator.Divide:
                    result = "/";
                    break;
                default:
                    result = "?";
                    break;
            }
            return result;
        }

        private static string FormatCompareOperator(VariableCondition condition)
        {
            string result;
            CompareOperator compareOperator = ReflectionUtil.GetCompareOperatorFromVariableCondition(condition);
            switch (compareOperator)
            {
                case CompareOperator.Equals:
                    result = "==";
                    break;
                case CompareOperator.NotEquals:
                    result = "!=";
                    break;
                case CompareOperator.LessThan:
                    result = "<";
                    break;
                case CompareOperator.GreaterThan:
                    result = ">";
                    break;
                case CompareOperator.LessThanOrEquals:
                    result = "<=";
                    break;
                case CompareOperator.GreaterThanOrEquals:
                    result = ">=";
                    break;
                default:
                    result = "?";
                    break;
            }
            return result;
        }
    }
}
